#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cfloat>
#include <vector>

#include "../ObjLoader/objLoader.h"
#include "../GLRenderer.h"
#include "../Math/Helpers.h"
#include "../Math/Intersections.h"

#include "Hemisphere.h"

using namespace std;

#define PushVec(list, vec){\
	list->push_back(vec[0]);list->push_back(vec[1]);list->push_back(vec[2]);\
}\

typedef struct{
	float Verts[15];
	float Normal[3];
	int Index;
	int End;
	int Width;
	int Height;
}RootQuad;

class RadioScene{
	public:
		RadioScene(objLoader* objScene, float subFactor){
			int quadCount;

			printf("...%d\n", objScene->normalCount);

			vector<float>* _tempVerts = new vector<float>();
			vector<float>* _tempNorms = new vector<float>();
			vector<float>* _tempTans = new vector<float>();
			vector<float>* _tempEnergy = new vector<float>();
			vector<float>* _tempReflectivity = new vector<float>();

			// build scene
			for(int i = 0; i < objScene->faceCount; i++){
				obj_face* f = objScene->faceList[i];
				float patchSub = subFactor;

				if(f->vertex_count == 4){
		            obj_vector* v1 = objScene->vertexList[f->vertex_index[0]];
		            obj_vector* v2 = objScene->vertexList[f->vertex_index[1]];
		            obj_vector* v3 = objScene->vertexList[f->vertex_index[2]];
		            obj_vector* v4 = objScene->vertexList[f->vertex_index[3]];
					obj_vector* norm = objScene->normalList[f->normal_index[0]];

		            float temp[15] = {
		                            (float)v1->e[0], (float)v1->e[1], (float)v1->e[2],
		                            (float)v2->e[0], (float)v2->e[1], (float)v2->e[2],
		                            (float)v3->e[0], (float)v3->e[1], (float)v3->e[2],
		                            (float)v4->e[0], (float)v4->e[1], (float)v4->e[2],
		                            (float)v1->e[0], (float)v1->e[1], (float)v1->e[2],
		            };

					// get the normal
					float tempNorm[3] = {
						(float)norm->e[0], (float)norm->e[1], (float)norm->e[2],
					};
					VecNorm(tempNorm);

					// calulate the tangent
					float tempTan[3] = {0};
					float tanLen;
					VecSub(tempTan, temp, (temp + 3));
					VecLen(tempTan, tanLen); VecScl(tempTan, (1.0f/tanLen));

					// Get energy
					float energy[3] = {0};
					obj_material* m = objScene->materialList[f->material_index];
					printf("Mat Name: '%s'\n", m->name);

					// Get the reflectivity
					float reflectivity[3] = {0};

					for(int i = 3; i--;){
						energy[i] = 0;
						reflectivity[i] = 1.0f - (float)m->diff[i];
					}

					// and the area
					float area = quadArea(temp);
					int patchSide = (int)(sqrt(area) * patchSub);

					if(strcmp(m->name, "light\n") == 0){

						for(int i = 3; i--;){
							StartingEnergy = energy[i] = 100;
							reflectivity[i] = 1.0f;
						}

						patchSide = 3; // make light sources much finer
						//if(energy > 0) printf("energy: %f (%f, %f, %f)\n", energy, (float)m->diff[0], (float)m->diff[1], (float)m->diff[2]);
					}

					// setup the root quad for all the patches
					RootQuad* quad = new RootQuad();
					memcpy(quad->Verts, temp, sizeof(float) * 15);
					memcpy(quad->Normal, tempNorm, sizeof(float) * 3);
					quad->Index = _tempVerts->size() / 15;
					quad->Width = quad->Height = patchSide;

					// subdivide
					{
						float left[3] = {0}, top[3] = {0};
						float leftLen, topLen;

						VecSub(left, temp, (temp + 3));
						VecSub(top, temp, (temp + 9));

						VecLen(left, leftLen);
						VecLen(top, topLen);
						VecScl(left, 1.0f / patchSide);
						VecScl(top, 1.0f / patchSide);

						printf("\tLeft len:%f Top len: %f\n", leftLen, topLen);

						float dx = 1.0f / (float)patchSide;
						float dy = dx;
						printf("Delta: %f Side: %d\n", dx, patchSide);
						//VecPrint()
						//patchSide = 1;
						// add all patches
						float base[3] = {0};
                        VecLerp(base, temp, (temp + 3), 0);
                        VecLerp(left, temp, (temp + 9), 0);
                        VecAdd(base, base, left);

						for(int y = 0; y < patchSide; y++)//for(int y = patchSide; y--;)
							for(int x = 0; x < patchSide; x++){//for(int x = patchSide; x--;){
                                float corner[3] = {0};
                                float vert[3] = {0};
                                float diff[3] = {0};
                                float dLeft[3] = {left[0],left[1],left[2]};
                                float dTop[3] = {top[0],top[1],top[2]};

                                VecScl(dLeft, x); VecScl(dTop, y);
                                // top left corner of quad
                                //VecAdd(vert, temp, dLeft);
                                //VecAdd(vert, vert, dTop);
                                VecLerp(vert, temp, (temp + 3), x * dx);
                                VecLerp(corner, temp, (temp + 9), y * dy);
                                VecAdd(vert, vert, corner);
                                VecSub(diff, temp, base);
                                VecAdd(vert, vert, diff);
                                PushVec(_tempVerts, vert);

                                // top right corner
                                //VecAdd(vert, vert, left);
                                VecLerp(vert, temp, (temp + 3), (x + 1) * dx);
                                VecLerp(corner, temp, (temp + 9), y * dy);
                                VecAdd(vert, vert, corner);
                                VecAdd(vert, vert, diff);
                                PushVec(_tempVerts, vert);

                                // bottom right corner
                                VecLerp(vert, temp, (temp + 3), (x + 1) * dx);
                                VecLerp(corner, temp, (temp + 9), (y + 1) * dy);
                                VecAdd(vert, vert, corner);
                                VecAdd(vert, vert, diff);
                                PushVec(_tempVerts, vert);                                                              

                                // bottom left corner
                                //VecSub(vert, vert, left);
                                VecLerp(vert, temp, (temp + 3), (x) * dx);
                                VecLerp(corner, temp, (temp + 9), (y + 1) * dy);
                                VecAdd(vert, vert, corner);
                                VecAdd(vert, vert, diff);
                                PushVec(_tempVerts, vert);

                                // close the quad
                                //PushVec(_tempVerts, corner);
                                VecLerp(vert, temp, (temp + 3), x * dx);
                                VecLerp(corner, temp, (temp + 9), y * dy);
                                VecAdd(vert, vert, corner);
                                VecAdd(vert, vert, diff);
                                PushVec(_tempVerts, vert);

								// add the normal
								PushVec(_tempNorms, tempNorm);

								// add the tangent
								PushVec(_tempTans, tempTan);

								// add the energy for this patch
								for(int i = 0; i < 3; i++){
									_tempEnergy->push_back(energy[i]);
									_tempReflectivity->push_back(reflectivity[i]);
								}
							}
					}

					quad->End = _tempVerts->size() / 15;
					RootQuads.push_back(*quad);

					printf("\t@%d Area: %f\n\t\tSide length: %d\n", i, area, patchSide);


				}
			}

			for(int i =0; i < RootQuads.size(); i++){
				RootQuad q = RootQuads[i];
				printf("@%d - [%d, %d)\n", i, q.Index, q.End);
			}

			_patchCount = quadCount = _tempVerts->size() / 15;

			_patchVerts = new float[quadCount * 15];
			_patchNorms = new float[quadCount * 3];
			_patchTangents = new float[quadCount * 3];

			for(int i = _tempVerts->size(); i--;){
				_patchVerts[i] = (*_tempVerts)[i];
			}

			printf("NL: %d\n", _tempNorms->size()/3);
			printf("Root quads: %d\n", RootQuads.size());

			for(int i = quadCount * 3; i--;){
				_patchNorms[i] = (*_tempNorms)[i];
				_patchTangents[i] = (*_tempTans)[i];
			}

			delete _tempVerts;
			delete _tempNorms;
			delete _tempTans;

			generateDisplayNorms();

			printf("Allocating form matrix %d x %d\n", quadCount, quadCount);
			// allocat the form matrix and energy array
			_patchForm = new float*[quadCount];
			_patchEnergy = new float*[quadCount];
			_patchReflectivity = new float*[quadCount];
			_patchRadiosity = new float*[quadCount];

			// allocate all stuffs
			_iterations = 0;

			for(int i = quadCount; i--;){
				_patchEnergy[i] = new float[3];
				_patchReflectivity[i] = new float[3];
				_patchRadiosity[i] = new float[3];
				_patchForm[i] = new float[_patchCount];
				for(int j = quadCount; j--;) _patchForm[i][j] = 0;

				bzero(_patchRadiosity[i], 3 * sizeof(float));

				for(int j = 3; j--;){
					int vi3 = i * 3 + j;
					_patchEnergy[i][j] = (*_tempEnergy)[vi3];
					_patchReflectivity[i][j] = (*_tempReflectivity)[vi3];
				}
			}

			delete _tempEnergy;
			delete _tempReflectivity;

			printf("Form size: %d\n", _patchCount * _patchCount * sizeof(float));
		}

		int ComputePatchForm(int index, Hemisphere* hemi, Matrix4 orientation){
			int intersections = 0;
			float* c = _patchCenters + (index * 3);
			float* n = _patchNorms + (index * 3);
			Cell* cells = hemi->GetCells();
			hemi->Orient(&orientation); // orient for this patch
			float* form = _patchForm[index];

			// for every patch in the scene
			for(int i = _patchCount; i--;){
				if(i!=index){
					// create a ray from this patch to the
					// current patch

					// calculate the direction to the current patch
					float* thisCenter = _patchCenters + (i * 3);
					float* thisNormal = _patchNorms + (i * 3);
					float dir[3] = {0}, dist;

					VecSub(dir, thisCenter, c);
					VecLen(dir, dist);
					VecDirTo(dir, thisCenter, c);
					Ray r = {
						{thisCenter[0], thisCenter[1], thisCenter[2]},
						{dir[0], dir[1], dir[2]}
					};

					// sweet! lets assume nothing is obstructing the ray between
					// the current patch and this patch. Time to find
					// the power of the incomming light
					float dot = 0;
					//printf("\n");
					//VecPrint(n);printf("\n");
					//VecPrint(dir);printf("\n");
					VecDot(dot, n, dir);
					//dot += (float)1.0f;
					if(dot < 0) dot = -dot;
					_patchForm[index][i] = dot / dist;//(float)abs(dot);

					// get ready to see if there is anything between the current
					// patch and this patch
					int nearestIndex = index;
					float nearestDist = 0;

					if(nearestIndex != nearestPatch(r))
						_patchForm[index][i] = 0;

/*
					// get the intersection distance between this patch
					// and the current patch
					nearestDist = QuadIntersection(
						_patchVerts + (index * 15),
						_patchNorms + (index * 3),
						r
					);

					// is there a patch which is obstructing
					// the line of sight between this patch
					// and the current?
					for(int pi = _patchCount; pi--;){
						float t = QuadIntersection(
							_patchVerts + (pi * 15),
							_patchNorms + (pi * 3),
							r
						);

						if(t < nearestDist && t > 0){
							float theta = 0;
							//VecDot(theta, (_patchNorms + (index * 3)), dir);
							//if(theta <= 0) // vectors are not pointing in a similar direction
							 _patchForm[index][i] = 0;
							continue;
						}
					}
*/
				}
			}
			return 1;
		}

		void SaveDisplayData(const char* path){
			FILE* fp = fopen(path, "wb");

			if(fp){
				fwrite(&_patchCount, sizeof(int), 1, fp); // number of patches

				// save the patch positions
				for(int i = _patchCount; i--;){
					fwrite((_patchCenters + i * 3), sizeof(float), 3, fp); 
				}

				// save the patch energy array
				for(int i = _patchCount; i--;){
					fwrite(_patchRadiosity[i], sizeof(float), 3, fp); 
				}

				// save the quad count
				int rootQuadCount = RootQuads.size();
				fwrite(&rootQuadCount, sizeof(int), 1, fp);

				// save the quads
				for(int i = 0; i < rootQuadCount; i++){
					RootQuad r = RootQuads[i];
					fwrite(&r, sizeof(RootQuad), 1, fp);
				}

				fclose(fp);
			}
		}

		void SaveForm(const char* path){
			FILE* fp = fopen(path, "wb");

			if(fp){
				fwrite(&_patchCount, sizeof(int), 1, fp); // width
				fwrite(&_patchCount, sizeof(int), 1, fp); // height
				//fprintf(fp, "%d %d\n", _patchCount, _patchCount);

				// save the patch form matrix
				for(int i = 0; i < _patchCount; i++){
					fwrite(_patchForm[i], sizeof(float), _patchCount, fp);
					/*for(int j = _patchCount; j--;){
						fprintf(fp, "%f ", _patchForm[j][i]);
					}
					fprintf(fp, "\n");*/
				}

				// save the patch energy array
				for(int i = 0; i < _patchCount; i++){
					float* row = _patchEnergy[i];
					fwrite(row, sizeof(float), 3, fp); 
				}

				fclose(fp);
			}
		}

		int LoadForm(const char* path){
			FILE* fp = fopen(path, "rb");

			if(fp){
				int size = 0;
				fread(&size, sizeof(int), 1, fp); // width
				fread(&size, sizeof(int), 1, fp); // height

				if(_patchCount == size){ // correct dimensions
					// load the patch form matrix
					for(int i = 0; i < _patchCount; i++){
						fread(_patchForm[i], sizeof(float), _patchCount, fp);
					}

					// load the patch energy array
					// save the patch energy array
					for(int i = 0; i < _patchCount; i++){
						fread(_patchEnergy[i], sizeof(float), 3, fp); 
					}
				}
				else{
					fclose(fp);
					printf("%d != %d Failed to load..\n", _patchCount, size);
					return 0;
				}

				fclose(fp);

				return 1;
			}

			return 0;
		}

        void Reduce(){
        	for(int ci = 0; ci < 3; ci++){
                int maxIndex = -1;
                float maxEnergy = 0;

                for(int i = _patchCount; i--;){
                        if(_patchEnergy[i][ci] > maxEnergy){
                                maxIndex = i;
                                maxEnergy = _patchEnergy[i][ci];
                        }
                }

                if(maxIndex < 0){
                        continue;
                }

				if(maxEnergy == StartingEnergy)
				_patchRadiosity[maxIndex][ci] += _patchEnergy[maxIndex][ci] * (_patchReflectivity[maxIndex][ci]);
				else
                _patchRadiosity[maxIndex][ci] += _patchEnergy[maxIndex][ci] * (1.0f - _patchReflectivity[maxIndex][ci]);
                _patchEnergy[maxIndex][ci] = 0;


                if(maxEnergy == StartingEnergy)
                	maxEnergy *= (_patchReflectivity[maxIndex][ci]); // energy to be transmitted
                else
                	maxEnergy *= 1.0f - _patchReflectivity[maxIndex][ci];//(_patchReflectivity[maxIndex][ci]); // energy to be transmitted

                float* form = _patchForm[maxIndex];
                for(int j = _patchCount; j--;){ // look at every other patch
                        if(maxIndex!=j){
                                _patchEnergy[j][ci] += form[j] * maxEnergy;
                        }
                }

                CurrentMaxEnergy[ci] = 0;
                CurrentMaxRadiosity[ci] = 0;
                for(int i = _patchCount; i--;){
                        if(_patchRadiosity[i][ci] > CurrentMaxRadiosity[ci]){
                                CurrentMaxRadiosity[ci] = _patchRadiosity[i][ci];
                        }
                        if(_patchEnergy[i][ci] > CurrentMaxEnergy[ci]){
                                CurrentMaxEnergy[ci] = _patchEnergy[i][ci];
                        }
                }

                if(_iterations % 1000 == 0){
                        printf("Itteration: %d\n", _iterations);
                        printf("%d - %f\n", maxIndex, CurrentMaxEnergy[ci]);
                }
            }
                _iterations++;
        }

		/*void Reduce(){
			int maxIndex = -1;
			for(int c = 0; c < 3; c++){
				maxIndex = -1;
				float maxEnergy = 0;
				for(int i = _patchCount; i--;){
					if(_patchEnergy[i][c] > maxEnergy){
						maxIndex = i;
						maxEnergy = _patchEnergy[i][c];
					}
				}

				if(maxIndex < 0){
					return;
				}

				if(_patchEnergy[maxIndex][c] == StartingEnergy)
					_patchRadiosity[maxIndex][c] = 0.1f;
				else
					_patchRadiosity[maxIndex][c] += _patchEnergy[maxIndex][c] * (1.0f - _patchReflectivity[maxIndex][c]);


				maxEnergy *= _patchReflectivity[maxIndex][c]; // energy to be transmitted


				if(_patchReflectivity[maxIndex][c] > 0.49f && _patchReflectivity[maxIndex][c] < 0.51f){
					printf("Red: %f --> %f\n", _patchEnergy[maxIndex][c], maxEnergy);
				}
				_patchEnergy[maxIndex][c] = 0;

				float* form = _patchForm[maxIndex];
				for(int j = _patchCount; j--;){ // look at every other patch
					if(maxIndex!=j){
						_patchEnergy[j][c] += form[j] * maxEnergy;
					}
				}

				CurrentMaxEnergy[c] = 0;
				CurrentMaxRadiosity[c] = 0;

				for(int i = _patchCount; i--;){
					if(_patchRadiosity[i][c] > CurrentMaxRadiosity[c]){
						CurrentMaxRadiosity[c] = _patchRadiosity[i][c];
					}
					if(_patchEnergy[i][c] > CurrentMaxEnergy[c]){
						CurrentMaxEnergy[c] = _patchEnergy[i][c];
					}
				}
			}
			_iterations++;

			if(_iterations % 1000 == 0){
				printf("Itteration: %d\n", _iterations);
				printf("%d - %f\n", maxIndex, CurrentMaxEnergy[0]);
			}
		}*/
#pragma region Getters
		int GetPatchCount(){
			return _patchCount;
		}

		float* GetPatchVerts(){
			return _patchVerts;
		}

		float* GetPatchNorms(){
			return _patchNorms;
		}

		float* GetPatchTangents(){
			return _patchTangents;
		}

		float* GetPatchCenters(){
			return _patchCenters;
		}

		float** GetEnergies(){
			return _patchEnergy;
		}

		float** GetRadiosity(){
			return _patchRadiosity;
		}

		float** GetReflectivities(){
			return _patchReflectivity;
		}

		float** GetForm(){
			return _patchForm;
		}
#pragma endregion

		GLuint DisplayNorms;
		float StartingEnergy;
		float CurrentMaxEnergy[3];
		float CurrentMaxRadiosity[3];
		std::vector<RootQuad> RootQuads;
	private:
		int _iterations;
		int _patchCount;

		int _quads;

		float* _patchVerts;
		float* _patchCenters;
		float* _patchNorms;
		float* _patchTangents;

		float** _patchForm;
		float**  _patchEnergy;
		float**  _patchReflectivity;
		float** _patchRadiosity;

		float quadArea(float* quad){
			float diff[3];
			float* v1 = quad;
			float* v3 = quad + 6;

			VecSub(diff, v1, v3);

			//VecPrint(v1); printf(" - "); VecPrint(v3); printf(" = "); VecPrint(diff); printf("\n");

			return abs(2 * diff[0] * diff[1] + 2 * diff[0] * diff[2] + 2 * diff[1] * diff[2]);
		}



		int nearestPatch(Ray r, int ignore){

			// find the closest root quad
			int rootIndex = -1;
			/*float rootDistance = FLT_MAX;
			for(int i = RootQuads.size(); i--;){
				RootQuad q = RootQuads[i];

				if(q.Index <= ignore && q.End > ignore)
					continue;

				float t = QuadIntersection(
					q.Verts,                
					q.Normal,
					r
				);

				if(t < rootDistance && t > 0){
					rootDistance = t;
					rootIndex = i;
				}
			}
			if(rootIndex < 0){
				return -1;
			}*/

			// find the closest patch
			int patchIndex = -1;
			float patchDistance = FLT_MAX;
			for(rootIndex = 0; rootIndex < RootQuads.size(); rootIndex++){
			RootQuad root = RootQuads[rootIndex];
			for(int i = root.Index; i < root.End; i++){
				//if(i != ignore){
					float t = QuadIntersection(
						(_patchVerts + (i * 15)),
						(_patchNorms + (i * 3)),
						r
					);

					if(t < patchDistance && t > 0){
						patchDistance = t;
						patchIndex = i;
					}
				//}
				return patchIndex;
			}
			}
			if(patchIndex < 0){
				exit(1);
				return -1;
			}
			else return patchIndex;
		}


		int nearestPatch(Ray r){
			int patchIndex = -1;
			float patchDistance = FLT_MAX;
			for(int i = 0; i < _patchCount; i++){
				float t = QuadIntersection(
					(_patchVerts + i * 15),
					(_patchNorms + i * 3),
					r
				);

				if(t < patchDistance && t > 0){
					patchDistance = t;
					patchIndex = i;
				}
			}
			if(patchIndex < 0) return -1;
			else return patchIndex;
		}

		void generateDisplayNorms(){
			float* verts = new float[_patchCount * 6];
			_patchCenters = new float[_patchCount * 3];

			for(int i = 0; i < _patchCount; i++){
				float* q = _patchVerts + (i * 15);
				float* n = verts + (i * 6);
				float* c = _patchCenters + (i * 3);
				float* normal = _patchNorms + (i * 3);
				float vert[3] = {0};
				float norm[3] = {normal[0],normal[1],normal[2]};

				// calulate the center of the patch
				VecAdd(vert, q, (q+6));
				VecScl(vert, 0.5f);
				memcpy(n, vert, sizeof(float) * 3);
				VecScl(norm, 0.001f);
				VecAdd(vert, vert, norm);
				memcpy(c, vert, sizeof(float) * 3);

				// add the length of the normal
				VecAdd(vert, vert, normal);
				memcpy(n + 3, vert, sizeof(float) * 3);
			}
			printf("yeah\n");

			GLuint normVerts;
			glGenVertexArrays(1, &normVerts);
			glBindVertexArray(normVerts);

			glGenBuffers(1, &DisplayNorms);
			glBindBuffer(GL_ARRAY_BUFFER, DisplayNorms);
			glBufferData(GL_ARRAY_BUFFER, 6 * _patchCount * sizeof(float), verts, GL_STATIC_DRAW);

			delete verts;
		}
		
};
